using System;
using System.Collections.Generic;
using System.Text;

using libsecondlife;
using libsecondlife.Packets;

namespace Feathertail.Tracker
{
    public class ObjectOverlord : libsecondlife.ObjectManager
    {
        public enum TrackerStatus { LastReceivedPacket, SimsTracked, CurrentSimObjects };
        protected DateTime LastPacketReceived;

        protected Dictionary<Simulator, Dictionary<uint, LLObject>> SimObjectLookup = new Dictionary<Simulator, Dictionary<uint, LLObject>>();
        protected Dictionary<LLUUID, LLObject> ObjectUUIDLookup = new Dictionary<LLUUID, LLObject>();
        protected Dictionary<LLObject, Simulator> ObjectSimLookup = new Dictionary<LLObject, Simulator>();

        protected Dictionary<string, List<ObjectFilter>> RunningFilters = new Dictionary<string, List<ObjectFilter>>();
        protected Dictionary<string, List<LLObject>> RunningFilterResults = new Dictionary<string, List<LLObject>>();

        protected bool FlushSimPrimsOnDisconnect = true;

        protected List<LLUUID> PropertyRequestQueue = new List<LLUUID>();


        public delegate void OnRunningFilterHitCallback(Simulator simulator, string filterName, LLObject obj);
        public event OnRunningFilterHitCallback OnRunningFilterHit;


        public ObjectOverlord(SecondLife client) : base(client)
        {
            Client.Settings.ALWAYS_DECODE_OBJECTS = true;
            Client.Settings.ALWAYS_REQUEST_OBJECTS = true;

            Client.Network.RegisterCallback(PacketType.KillObject, new NetworkManager.PacketCallback(MyKillObjectHandler));
            Client.Network.RegisterCallback(PacketType.ObjectPropertiesFamily, new NetworkManager.PacketCallback(MyObjectPropertiesFamilyHandler));

        }


        #region Filtering

        public List<LLObject> RunningFilterGetResults(string name)
        {
            if (RunningFilterResults.ContainsKey(name))
            {
                return RunningFilterResults[name];
            }
            else
            {
                return new List<LLObject>();
            }
        }

        public void RunningFilterClear(string name)
        {
            RunningFilters.Remove(name);
            RunningFilterResults.Remove(name);
        }

        public void RunningFilterSet(string name, List<ObjectFilter> filters)
        {
            RunningFilters[name] = filters;
            RunningFilterResults[name] = FilterObjects(filters);
        }

        /// <summary>
        /// Filter tracked objects
        /// </summary>
        /// <param name="filters">Filters to apply</param>
        /// <returns></returns>
        public List<LLObject> FilterObjects(List<ObjectFilter> filters)
        {
            List<LLObject> Results = new List<LLObject>();
            if (SimObjectLookup.ContainsKey(Client.Network.CurrentSim))
            {
                foreach (LLObject prim in SimObjectLookup[Client.Network.CurrentSim].Values)
                {
                    bool include = true;
                    foreach (ObjectFilter filter in filters)
                    {
                        if (!filter.IncludeObject(Client,  prim))
                        {
                            include = false;
                            break;
                        }
                    }

                    if (include)
                    {
                        Results.Add(prim);
                    }
                }
            }

            Results.Sort(new DistanceFromLocal(Client.Self.Position));
            return Results;
        }
        public List<LLObject> FilterObjects(ObjectFilter filter)
        {
            List<ObjectFilter> filters = new List<ObjectFilter>(1);
            filters.Add(filter);
            return FilterObjects(filters);
        }

        /// <summary>
        /// Scan object/avatar cache data for prims
        /// </summary>
        /// <param name="radius"></param>
        /// <param name="info"></param>
        /// <returns></returns>
        [Obsolete]
        public List<LLObject> ScanPrims(double radius, out string info)
        {
            info = "No Objects Found Yet";

            List<LLObject> ScanResults = new List<LLObject>();

            if (SimObjectLookup.ContainsKey(Client.Network.CurrentSim))
            {
                info = SimObjectLookup[Client.Network.CurrentSim].Values.Count + " objects checked.";
                foreach (LLObject prim in SimObjectLookup[Client.Network.CurrentSim].Values)
                {
                    double distance = Client.Self.Position.GetDistanceTo(prim.Position);
                    if (distance < radius)
                    {
                        ScanResults.Add(prim);
                    }
                }
            }

            ScanResults.Sort(new DistanceFromLocal(Client.Self.Position));
            return ScanResults;
        }

        #endregion

        /// <summary>
        /// Get some status information about the tracker
        /// </summary>
        /// <returns></returns>
        public Dictionary<TrackerStatus, object> GetTrackerStatus()
        {
            Dictionary<TrackerStatus, object> status = new Dictionary<TrackerStatus, object>();
            status[TrackerStatus.LastReceivedPacket] = LastPacketReceived;
            status[TrackerStatus.SimsTracked] = SimObjectLookup.Count;

            if (SimObjectLookup.ContainsKey(Client.Network.CurrentSim))
            {
                status[TrackerStatus.CurrentSimObjects] = SimObjectLookup[Client.Network.CurrentSim].Count;
            }
            else
            {
                status[TrackerStatus.CurrentSimObjects] = 0;
            }

            Console.WriteLine("Current Sim: " + Client.Network.CurrentSim.Name + "[" + Client.Network.CurrentSim.Handle + "] :: " + SimObjectLookup.ContainsKey(Client.Network.CurrentSim));

            foreach (Simulator sim in SimObjectLookup.Keys)
            {
                Console.WriteLine(sim.Name + "[" + sim.Handle + "]");
            }

            return status;
        }

        protected Dictionary<uint, LLObject> GetSimObjLookup(Simulator simulator)
        {
            // If nessesary, add new dictionaries for objects & avatars in this sim.
            if (!SimObjectLookup.ContainsKey(simulator))
            {
                SimObjectLookup[simulator] = new Dictionary<uint, LLObject>();
            }

            // Get dictionaries for avatars & objects in this sim.
            return SimObjectLookup[simulator];
        }


        #region Superclass overrides

        /// <summary>
        /// Primitive Factory, this allows a subclass to lookup a copy of the Primitive
        /// and return it for updating, rather then always creating a new Primitive
        /// </summary>
        /// <param name="simulator"></param>
        /// <param name="LocalID"></param>
        /// <param name="UUID"></param>
        /// <returns></returns>
        override protected Primitive GetPrimitive(Simulator simulator, uint LocalID, LLUUID UUID)
        {
            Dictionary<uint, LLObject> SimObjects = GetSimObjLookup(simulator);

            Primitive prim;

            // Check local sim
            if (SimObjects.ContainsKey(LocalID))
            {
                prim = (Primitive)SimObjects[LocalID];
            }
            else if ((UUID != null) && ObjectUUIDLookup.ContainsKey(UUID))
            { // Check if we've got a record for the prim at all

                prim = (Primitive)ObjectUUIDLookup[UUID];

                // Looks like we're already tracking this object, lets make sure we track it in the correct sim
                ObjectSimLookup[prim] = simulator;
                SimObjects[prim.LocalID] = prim;

            }
            else
            {
                prim = new Primitive();

                if (UUID != null)
                {
                    if (ObjectUUIDLookup == null)
                    {
                        Console.WriteLine("ObjectUUIDLookup is null??");
                    }
                    if (UUID == null)
                    {
                        Console.WriteLine("UUID is null??");
                    }
                    if (prim == null)
                    {
                        Console.WriteLine("prim is null??");
                    }
                    ObjectUUIDLookup[UUID] = prim;
                }

                ObjectSimLookup[prim] = simulator;
                SimObjects[LocalID] = prim;
            }

            // If the prim doesn't have Properties set, request them
            if ((prim.PropertiesFamily.ObjectID == null) && (UUID != null) && (UUID != LLUUID.Zero))
            {
                lock (PropertyRequestQueue)
                {
                    if (!PropertyRequestQueue.Contains(UUID))
                    {
                        PropertyRequestQueue.Add(UUID);
                        base.RequestObjectPropertiesFamily(simulator, UUID, false);
                    }
                }
            }

            return prim;
        }


        /// <summary>
        /// Primitive Factory, this allows a subclass to lookup a copy of the Avatar
        /// and return it for updating, rather then always creating a new Avatar
        /// </summary>
        /// <param name="simulator"></param>
        /// <param name="LocalID"></param>
        /// <param name="UUID"></param>
        /// <returns></returns>
        override protected Avatar GetAvatar(Simulator simulator, uint LocalID, LLUUID UUID)
        {
            Dictionary<uint, LLObject> SimObjects = GetSimObjLookup(simulator);

            Avatar avatar;

            // Check local sim
            if (SimObjects.ContainsKey(LocalID))
            {
                avatar = (Avatar)SimObjects[LocalID];
            }
            else if ((UUID != null) && ObjectUUIDLookup.ContainsKey(UUID))
            { // Check if we've got a record for the prim at all

                avatar = (Avatar)ObjectUUIDLookup[UUID];

                // Looks like we're already tracking this object, lets make sure we track it in the correct sim
                ObjectSimLookup[avatar] = simulator;
                SimObjects[avatar.LocalID] = avatar;

            }
            else
            {
                avatar = new Avatar();

                if (UUID != null)
                {
                    ObjectUUIDLookup[UUID] = avatar;
                }

                ObjectSimLookup[avatar] = simulator;
                SimObjects[LocalID] = avatar;
            }

            // If the prim doesn't have Properties set, request them
            if ((avatar.PropertiesFamily.ObjectID == null) && (UUID != null) && (UUID != LLUUID.Zero))
            {
                lock (PropertyRequestQueue)
                {
                    if (!PropertyRequestQueue.Contains(UUID))
                    {
                        PropertyRequestQueue.Add(UUID);
                        base.RequestObjectPropertiesFamily(simulator, UUID, false);
                    }
                }
            }

            return avatar;
        }

        /// <summary>
        /// Used to flag to a subclass that an LLObject was updated/created
        /// </summary>
        /// <param name="simulator"></param>
        /// <param name="obj"></param>
        override protected void llObjectUpdated(Simulator simulator, LLObject obj)
        {
            foreach (KeyValuePair<string, List<ObjectFilter>> RunningFilter in RunningFilters)
            {
                bool Match = true;
                foreach (ObjectFilter filter in RunningFilter.Value)
                {
                    if (filter.IncludeObject(Client, obj) == false)
                    {
                        Match = false;
                        break;
                    }
                }

                if (Match)
                {
                    if (RunningFilterResults[RunningFilter.Key].Contains(obj))
                    {
                        // Running Filter Results already contains object
                    }
                    else
                    {
                        // Running Filter doesn't contain, add
                        RunningFilterResults[RunningFilter.Key].Add(obj);
                    }

                    FireOnRunningFilterHit(simulator, RunningFilter.Key, obj);
                }
                else
                {
                    // Since it doesn't match, make sure that the results don't contain it
                    if (RunningFilterResults[RunningFilter.Key].Contains(obj))
                    {
                        // Get out of here!!!
                        RunningFilterResults[RunningFilter.Key].Remove(obj);
                    }
                }
            }
        }

        protected void FireOnRunningFilterHit(Simulator simulator, string filterName, LLObject obj)
        {
            if (OnRunningFilterHit != null)
            {
                OnRunningFilterHit(simulator, filterName, obj);
            }
        }

        #endregion

        #region Callback Handlers

        protected void MyKillObjectHandler(Packet packet, Simulator simulator)
        {
            // Get dictionaries for avatars & objects in this sim.
            Dictionary<uint, LLObject> SimObjects = GetSimObjLookup(simulator);

            LastPacketReceived = DateTime.Now;

            foreach (KillObjectPacket.ObjectDataBlock block in ((KillObjectPacket)packet).ObjectData)
            {
                if (SimObjects.ContainsKey(block.ID))
                {
                    SimObjects.Remove(block.ID);
                }
            }
        }

        protected void MyObjectPropertiesFamilyHandler(Packet p, Simulator sim)
        {
            ObjectPropertiesFamilyPacket op = (ObjectPropertiesFamilyPacket)p;

            lock (ObjectUUIDLookup)
            {
                if (!ObjectUUIDLookup.ContainsKey(op.ObjectData.ObjectID))
                {
                    return;
                }

                LLObject.ObjectPropertiesFamily props = ObjectUUIDLookup[op.ObjectData.ObjectID].PropertiesFamily;

                props.BaseMask = op.ObjectData.BaseMask;
                props.Category = op.ObjectData.Category;
                props.Description = Helpers.FieldToUTF8String(op.ObjectData.Description);
                props.EveryoneMask = op.ObjectData.EveryoneMask;
                props.GroupID = op.ObjectData.GroupID;
                props.GroupMask = op.ObjectData.GroupMask;
                props.LastOwnerID = op.ObjectData.LastOwnerID;
                props.Name = Helpers.FieldToUTF8String(op.ObjectData.Name);
                props.NextOwnerMask = op.ObjectData.NextOwnerMask;
                props.ObjectID = op.ObjectData.ObjectID;
                props.OwnerID = op.ObjectData.OwnerID;
                props.OwnerMask = op.ObjectData.OwnerMask;
                props.OwnershipCost = op.ObjectData.OwnershipCost;
                props.SalePrice = op.ObjectData.SalePrice;
                props.SaleType = op.ObjectData.SaleType;

                lock (PropertyRequestQueue)
                {
                    if (PropertyRequestQueue.Contains(op.ObjectData.ObjectID))
                    {
                        PropertyRequestQueue.Remove(op.ObjectData.ObjectID);
                    }
                }

                ObjectUUIDLookup[op.ObjectData.ObjectID].PropertiesFamily = props;
            }
        }


        /// <summary>
        /// Clear the object tracking for sims we disconnect from
        /// </summary>
        /// <param name="simulator"></param>
        /// <param name="reason"></param>
        void Network_OnSimDisconnected(Simulator simulator, NetworkManager.DisconnectType reason)
        {
            if (FlushSimPrimsOnDisconnect)
            {
                if (SimObjectLookup.ContainsKey(simulator))
                {
                    // Clear out any cached prim/object properties for these prims.
                    foreach (LLObject obj in SimObjectLookup[simulator].Values)
                    {
                        lock (ObjectUUIDLookup)
                        {
                            ObjectUUIDLookup.Remove(obj.ID);
                        }
                        ObjectSimLookup.Remove(obj);
                    }

                    // Removing this simulator *should* orphan it's entire contents and allow it to be GC'ed
                    SimObjectLookup.Remove(simulator);
                }
            }
        }


        #endregion
    }


    internal class DistanceFromGlobal : System.Collections.Generic.IComparer<LLObject>
    {
        private LLVector3d Source;

        public DistanceFromGlobal(LLVector3d OriginPt)
        {
            Source = OriginPt;
        }

        #region IComparer<LLObject> Members

        public int Compare(LLObject A, LLObject B)
        {
            uint GX, GY;

            LLVector3d PtA = new LLVector3d(A.Position);
            Helpers.LongToUInts(A.RegionHandle, out GX, out GY);
            PtA.X += GX;
            PtA.Y += GY;


            LLVector3d PtB = new LLVector3d(B.Position);
            Helpers.LongToUInts(A.RegionHandle, out GX, out GY);
            PtB.X += GX;
            PtB.Y += GY;

            return Source.GetDistanceTo(PtA).CompareTo(Source.GetDistanceTo(PtB));
        }
        #endregion
    }
    internal class DistanceFromLocal : System.Collections.Generic.IComparer<LLObject>
    {

        private LLVector3 Source;

        public DistanceFromLocal(LLVector3 OriginPt)
        {
            Source = OriginPt;
        }

        #region IComparer<LLObject> Members

        public int Compare(LLObject A, LLObject B)
        {
            return Source.GetDistanceTo(A.Position).CompareTo(Source.GetDistanceTo(B.Position));
        }

        #endregion
    }
    internal class LLVector3DistanceFromLocal : System.Collections.Generic.IComparer<LLVector3>
    {

        private LLVector3 Source;

        public LLVector3DistanceFromLocal(LLVector3 OriginPt)
        {
            Source = OriginPt;
        }

        #region IComparer<LLObject> Members

        public int Compare(LLVector3 A, LLVector3 B)
        {
            return Source.GetDistanceTo(A).CompareTo(Source.GetDistanceTo(B));
        }

        #endregion
    }
}
